Skip to content

fix(cli): serialize local start and stop per project (CLI-2875) - #7372

Merged
jgoux merged 5 commits into
developfrom
juliengoux/cli-2875-supabase-cli-21220-prunes-local-docker-network-before
Oct 30, 2026
Merged

jgoux merged 5 commits into
developfrom
juliengoux/cli-2875-supabase-cli-21220-prunes-local-docker-network-before

Conversation

@jgoux

@jgoux jgoux commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Addresses #7339

Problem

Between start creating a project's Docker network and attaching its first container, the network has no endpoints. Any project-label-filtered docker network prune that lands in that window removes it, and start then fails with network supabase_network_<id> not found. Within one invocation every prune runs before bring-up (stopped-stack recovery) or after it stops (rollback), so the prune in the report most likely came from an overlapping lifecycle command on the same project: an earlier start still rolling back, a stop, stop --all, or a db start rollback.

Change

  • start, db start (including the declarative auto-start), and stop hold a cross-process lifecycle lock under $SUPABASE_HOME/locks:
    • a per-project lock keyed by the project's Docker label, so checkouts sharing a project_id serialize and different projects still run in parallel;
    • a gate every per-project command holds shared and stop --all holds exclusively.
  • The lock is taken before the first Docker probe and released after any rollback, outside the rollback region, so a waiting command never rolls back another command's resources.
  • A blocked command prints one waiting line to stderr and fails with an actionable error after 10 minutes; waiting is interruptible.
  • The lock replaces the per-project start lock from fix(cli): recover start and db start from a stopped database container #7369, which it covers.
  • @supabase/stack gains holdSharedLock / holdExclusiveLock next to holdLock.
  • The ensureNetwork doc comment no longer claims the network cannot be removed mid-run.

Limitations

Older CLI binaries, raw docker commands, a different SUPABASE_HOME, and projects sharing a custom --network-id do not participate, and stop --all is not queued ahead of a stream of single-project commands (documented in start's SIDE_EFFECTS.md). db reset and the shadow database still create their network without the lock; that is a separate follow-up. When $SUPABASE_HOME/locks cannot be created or a lock file there cannot be opened, these commands continue without the lock and log a debug line.

A concurrent `stop`, `stop --all`, or rollback could prune a project's
Docker network between `start` creating it and attaching the first
container, failing `start` with "network ... not found".

`start`, `db start` (including declarative auto-start), and `stop` now
hold a cross-process lifecycle lock under `$SUPABASE_HOME/locks`: a
per-project lock keyed by the project's Docker label, behind a shared
gate that `stop --all` takes exclusively. A blocked command prints one
waiting line and gives up after 10 minutes.
@jgoux
jgoux requested a review from a team as a code owner October 28, 2026 13:08
@jgoux jgoux self-assigned this Oct 28, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

Verified all three Claude findings and confirmed them as nits: one misleading waiting message and two documentation gaps. Codex reported no findings. No functional defect was established. Tests were not run; dependencies are absent from the checkout.

Findings

Severity Location Category Sources Claim
⚪ NIT apps/cli/src/command-internal/project-lifecycle-lock.ts:131 ux claude When stop --all blocks the shared gate, the waiting message describes project-specific contention, while the eventual timeout correctly identifies an all-projects stop.
⚪ NIT apps/cli/src/commands/start/SIDE_EFFECTS.md:231 documentation claude The start, db start, and stop exit-code tables document ProjectLifecycleLockBusyError but omit the new ProjectLifecycleLockIoError failure.
⚪ NIT packages/stack/src/effect.ts:92 documentation claude The isLockBusy documentation mentions only holdLock even though the helper also classifies failures from holdSharedLock and holdExclusiveLock.

Stats

Claude findings: 3 · Codex findings: 0 · Confirmed: 3 · Refuted: 0 · Uncertain: 0


Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: auto · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread apps/cli/src/command-internal/project-lifecycle-lock.ts Outdated
Comment thread apps/cli/src/commands/start/SIDE_EFFECTS.md Outdated
Comment thread packages/stack/src/effect.ts

@Coly010 Coly010 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving so you're not blocked, one question though:

with an unwritable $SUPABASE_HOME, start and stop now fail before touching Docker (failed to create …/locks: PermissionDenied), where develop starts and stops fine. stop is the one i'd worry about most, since there's no way to tear the stack down from that environment at all

the start lock from #7369 carries on unlocked in that case (with a debug line) - any reason not to do the same here, at least for stop? totally fine to push back if you think a read-only home isn't worth supporting

jgoux added 2 commits October 29, 2026 18:18
# Conflicts:
#	apps/cli/src/command-internal/db-bootstrap/start-local-database.ts
#	apps/cli/src/commands/db/start/SIDE_EFFECTS.md
#	apps/cli/src/commands/start/SIDE_EFFECTS.md
@jgoux

jgoux commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, agreed. In d99df44 the lifecycle lock now behaves like the start lock from #7369: if $SUPABASE_HOME/locks can't be created or a lock file can't be opened, start, db start and stop continue unlocked with a debug line. A lock held by another command still fails after the 10-minute wait. Integration tests cover start and stop with an unwritable home. I also merged develop; since this lock already serialises start/db start per project, it replaces the #7369 start lock instead of adding a second one.

@jgoux
jgoux enabled auto-merge October 29, 2026 17:24
@github-actions

Copy link
Copy Markdown
Contributor

CLI benchmark: no changes found

Commit d99df44 against develop at b73be06 · 10 runs each · report only, never blocks merging

Command Before After Change
supabase --version 489 ms 487 ms no real change
supabase --help 414 ms 394 ms no real change
supabase init 287 ms 286 ms no real change
supabase migration new bench 377 ms 377 ms no real change
supabase gen types --lang typescript --project-id replayfixtureprojref 353 ms 369 ms no real change
supabase projects list 380 ms 348 ms no real change (-8%, within normal variation)
supabase functions list --project-ref replayfixtureprojref 423 ms 438 ms no real change

API calls: unchanged for all 7 commands.

Binary size: 133.8 MB, unchanged.

Details: timing ranges, API calls per command, slowest phases

Timing ranges

Command Before range After range Change Verdict
supabase --version 297-552 ms 324-567 ms -2 ms (0%) within noise
supabase --help 344-509 ms 339-520 ms -20 ms (-5%) within noise
supabase init 239-354 ms 241-463 ms -1 ms (0%) within noise
supabase migration new bench 261-623 ms 291-548 ms 0 ms (0%) within noise
supabase gen types --lang typescript --project-id replayfixtureprojref 274-452 ms 291-552 ms +16 ms (+5%) within noise
supabase projects list 295-459 ms 274-514 ms -32 ms (-8%) within noise
supabase functions list --project-ref replayfixtureprojref 296-541 ms 358-619 ms +16 ms (+4%) within noise

API calls per command

Command Before After Endpoint changes
supabase --version 0 0
supabase --help 0 0
supabase init 0 0
supabase migration new bench 0 0
supabase gen types --lang typescript --project-id replayfixtureprojref 2 2
supabase projects list 1 1
supabase functions list --project-ref replayfixtureprojref 2 2

Slowest phases

No command moved beyond noise, so there are no phase deltas.

Comment /bench to run it again on the latest commit.

Workflow run

@7ttp

7ttp commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

^ resolved the conflicts in ccb92c7 with no logic changes, the earlier auto merge only missed on the ghcr rate limit flake #7288 fixes...

@jgoux
jgoux added this pull request to the merge queue Oct 30, 2026
Merged via the queue into develop with commit c13428e Oct 30, 2026
54 checks passed
@jgoux
jgoux deleted the juliengoux/cli-2875-supabase-cli-21220-prunes-local-docker-network-before branch October 30, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants